/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.common.xml;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.XMLConstants;
import javax.xml.parsers.*;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

/**
 * 对 JAXP 简单封装
 *
 * @author vacoor
 * @see javax.xml.parsers
 */
public abstract class JAXP {
    private static final String SCHEMA_FEATURE = "http://apache.org/xml/features/validation/schema";

    /**
     * 根据给定的 DTD 或 Schema 创建一个 SAXParser
     * 如果希望捕获错误, Handler 可以继承 {@link ErrorCaptureHandler}
     *
     * @param dtdOrSchema
     * @return
     */
    public static SAXParser createSAXParser(URL dtdOrSchema) {
        try {
            return createSAXParser(getSchema(dtdOrSchema));
        } catch (Exception e) {
            throw new RuntimeException("Failed to create SAX Parser !", e);
        }
    }

    /**
     * 根据给定的 DTD 或 Schema 创建一个 DocumentBuilder 对象
     *
     * @param dtdOrSchema
     * @return
     */
    public static DocumentBuilder createDOMBuilder(URL dtdOrSchema) {
        try {
            return createDocumentBuilder(getSchema(dtdOrSchema));
        } catch (Exception e) {
            throw new RuntimeException("Failed to create DocumentBuilder !", e);
        }
    }

    private static Schema getSchema(URL dtdOrSchema) throws IOException, SAXException {
        if (dtdOrSchema == null) {
            return null;
        }
        return dtdOrSchema.getPath().endsWith(".dtd") ? createDTD(dtdOrSchema.openStream()) : createSchema(dtdOrSchema.openStream());
    }

    //
    private static SAXParser createSAXParser(Schema schema) throws ParserConfigurationException, SAXException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setXIncludeAware(true);
        factory.setNamespaceAware(true);
        /**
         * @deprecated
         * factory.setValidating(true);
         * factory.setFeature(SCHEMA_FEATURE, true);
         */
        factory.setSchema(schema);
        return factory.newSAXParser();
    }

    private static DocumentBuilder createDocumentBuilder(Schema schema) throws ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        // factory.setNamespaceAware(false);
        factory.setIgnoringElementContentWhitespace(true);
        factory.setIgnoringComments(true);
        factory.setCoalescing(true);
        /** @deprecated factory.setValidating(true);
        factory.setAttribute(SCHEMA_FEATURE, true); // validaing attr For Xerces implementation
         */
        factory.setSchema(schema);

        DocumentBuilder builder = factory.newDocumentBuilder();
        builder.setErrorHandler(new ErrorCaptureHandler());
        return builder;
    }

    // ---- schema
    private static Schema createDTD(InputStream dtd) throws SAXException {
        return createDTD(new StreamSource(dtd));
    }

    private static Schema createDTD(Source dtd) throws SAXException {
        return SchemaFactory.newInstance(XMLConstants.XML_DTD_NS_URI).newSchema(dtd);
    }

    private static Schema createSchema(InputStream schema) throws SAXException {
        return createSchema(new StreamSource(schema));
    }

    private static Schema createSchema(Source schema) throws SAXException {
        return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schema);
    }

    // ---- 错误捕获处理器
    private static class ErrorCaptureHandler extends DefaultHandler {
        private static final Logger LOG = LoggerFactory.getLogger(ErrorCaptureHandler.class);

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            LOG.debug("tag: {}", qName);
        }

        @Override
        public void warning(SAXParseException e) throws SAXException {
            // you can choose not to handle it
            throw new SAXException(getMessage("Warning", e));
        }

        @Override
        public void error(SAXParseException e) throws SAXException {
            throw new SAXException(getMessage("Error", e));
        }

        @Override
        public void fatalError(SAXParseException e) throws SAXException {
            throw new SAXException(getMessage("Fatal Error", e));
        }

        private String getMessage(String level, SAXParseException e) {
            return ("Parsing " + level + "\n" + "Line:    " + e.getLineNumber() + "\n"
                    + "Message: " + e.getMessage());
        }
    }
}
